/**
 * 
 */
package com.ibuonline.search.solr.score;

import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.lucene.index.IndexReader;
import org.apache.lucene.search.FieldCache;
import org.apache.solr.schema.IndexSchema;
import org.apache.solr.schema.Xscorer;

import com.greenpineyu.fel.Expression;

/**
 * 表达式评分器
 * 
 * @author Liuye
 * 
 */
@SuppressWarnings("serial")
public final class ExpressionXscorer implements Xscorer {
	private ExpressionXscorerFactory xscorerFactory;
	private String idField;
	private long[] idIndex;
	private long[] scatterIndex;
	private Map<String, float[]> innerDataIndex = new HashMap<String, float[]>();
	private Map<Long, Float> scatterCountMap = new HashMap<Long, Float>();
	private String scatterFieldName;

	public ExpressionXscorer(IndexSchema schema,
			ExpressionXscorerFactory xscorerFactory) {
		this.xscorerFactory = xscorerFactory;
		this.idField = schema.getUniqueKeyField().getName();
	}

	public ExpressionXscorer(IndexSchema schema,
			ExpressionXscorerFactory xscorerFactory, Expression expression,
			String scatterFieldName) {
		this.xscorerFactory = xscorerFactory;
		this.idField = schema.getUniqueKeyField().getName();
		this.scatterFieldName = scatterFieldName;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.apache.solr.schema.Xscorer#score(java.lang.String,
	 * java.util.Map)
	 */
	@Override
	public float score(int doc, float keywordScore) {
		// 根据id获取实际评分因子
		Long id = getIdValue(idIndex, doc);// String.valueOf(doc);//
		List<String> innerPropNames = xscorerFactory.getInnerExpression()
				.getPropNames();
		float innerScore = 0f;
		if (innerPropNames != null && !innerPropNames.isEmpty()) {
			Map<String, Object> env = new HashMap<String, Object>();
			for (String propName : innerPropNames) {
				if (!propName.startsWith("$")) {
					float[] index = innerDataIndex.get(propName);
					if (index != null) {
						env.put(propName, index[doc]);
					}
				}
			}
			env.put("$score", keywordScore);
			if (this.isScattered()) {
				env.put("$scatter", this.scatter(doc));
			}
			innerScore = xscorerFactory.getInnerExpression().eval(env);
		}
		float outerScore = this.xscorerFactory.getOuterScore(id);
		return innerScore + outerScore;

	}

	protected final long getIdValue(long[] index, int doc) {
		final long ord = index[doc];
		return ord == 0 ? null : ord;
	}

	@Override
	public void setNextReader(IndexReader reader, int base) throws IOException {
		idIndex = FieldCache.DEFAULT.getLongs(reader, idField);
		if (isScattered()) {
			scatterIndex = FieldCache.DEFAULT
					.getLongs(reader, scatterFieldName);
		}
		List<String> innerPropNames = xscorerFactory.getInnerExpression()
				.getPropNames();
		if (innerPropNames != null && !innerPropNames.isEmpty()) {
			for (String propName : innerPropNames) {
				if (!propName.startsWith("$")) {
					innerDataIndex.put(propName,
							FieldCache.DEFAULT.getFloats(reader, propName));
				}
			}
		}
	}

	public boolean isScattered() {
		return scatterFieldName != null && !"".equals(scatterFieldName.trim());
	}

	public float scatter(int doc) {
		final long scatterValue = scatterIndex[doc];
		if (scatterCountMap.containsKey(scatterValue)) {
			float count = scatterCountMap.get(scatterValue);
			count++;
			scatterCountMap.put(scatterValue, count);
			return 100F / count;
		} else {
			scatterCountMap.put(scatterValue, 1f);
			return 100F;
		}
	}

	public String getScatterFieldName() {
		return this.scatterFieldName;
	}

	public void setScatterFieldName(String fieldName) {
		this.scatterFieldName = fieldName;
	}
}
